﻿#include "genfunc.h"

QByteArray * bytes;
#define FUNCTIONCLASS ""
#define BASEIO "BASEIO"
#define NONARRAY -1

int convertInt(int value) {
    
    U4 initial, converted;
    initial.number = value;
    converted.number = value;
    
    for (int i = 0; i < 4; ++i) {
        
        converted.bytes[i] = initial.bytes[3 - i];
    }
    return converted.number;
}

short CalcOffset(int offset, int start)
{
    QByteArray ba;
    QDataStream str(&ba, QIODevice::ReadWrite);
    str << (short) offset;
    (*bytes)[start] = ba[0];
    (*bytes)[start+1] = ba[1];
    return 0;
}

void genCode(struct StProgram * root, struct SemanticProgram * program)
{
	QFile myclassFile;
    QDataStream stream(&myclassFile);

	QString filename = QString::QString(QString::QString(root->Id) + ".class");
	myclassFile.setFileName(filename);

	if(myclassFile.open(QIODevice::WriteOnly))
    {            
        
        writeBaseInfo(stream);

		writeConstants(stream, program->mainClass->constantsTable);
        
        stream << ACC_PUBLIC;
        
        stream << (unsigned short) 3;
        
        stream << (unsigned short)5;
        
        stream << (unsigned short) 0;
        
		writeFields(stream, program->mainClass->fieldsTable);
        
		writeMethods(stream, program->mainClass);
        
        stream << (unsigned short)0;
    }
    else
    {
        printf("Cannot open file\n");
        return ;
    }

	myclassFile.close();
}

void writeBaseInfo(QDataStream & s)
{
    /// Magic item, minor, major versions
	s << magic << minor_version << major_version;
}

void writeConstants(QDataStream & s, QList<SemanticConstant *> & constantsTable)
{
	int i, j;
	SemanticConstant* constant;
	unsigned char constant_code;
	unsigned short size = (unsigned short)(constantsTable.size() + 1);

	s << size;

	for(i = 0; i < constantsTable.size(); i++)
	{
		constant = constantsTable.at(i);
		constant_code = (unsigned char)constant->type;
		size = 0;
		QByteArray string;

		s << constant_code;

		switch(constant->type) 
		{
		case CONSTANT_INTEGER:
			s << constant->intConst;
			break;
		case CONSTANT_FLOAT:
			s << constant->floatConst;
			break;
		case CONSTANT_UTF8:
			size = constant->utf8Const.size();
			s << size;
			string.append(constant->utf8Const);
			for(j = 0; j < size; j++)
			{
				s << (unsigned char) string.at(j);
			}
			break;
		case CONSTANT_STRING:
		case CONSTANT_CLASS:
			s << (unsigned short)constant->const1->id;
			break;
		case CONSTANT_NAMEANDTYPE:
		case CONSTANT_FIELDREF:
		case CONSTANT_METHODREF:
			s << (unsigned short)constant->const1->id;
			s << (unsigned short)constant->const2->id;
			break;
		}
	}
}

void writeFields(QDataStream & s, QMap<QString, struct SemanticField *> & fields)
{
	unsigned short size = fields.size();

	s << size;

	QMapIterator<QString, struct SemanticField *> fieldIt(fields);
    while(fieldIt.hasNext())
    {
        struct SemanticField * field = fieldIt.next().value();

		s << (unsigned short)(ACC_PUBLIC|ACC_STATIC);

        s << (unsigned short)field->constFieldref->const2->const1->id;

        s << (unsigned short)field->constFieldref->const2->const2->id;

        s << (unsigned short)0;
    }
}

void writeMethods(QDataStream & s, struct SemanticClass * currentClass)
{
	unsigned short size = currentClass->methodsTable.size();

	s << size;

	QMapIterator<QString, struct SemanticMethod *> methodIt(currentClass->methodsTable);
	while(methodIt.hasNext())
    {
        struct SemanticMethod * method = methodIt.next().value();
        
		s << (unsigned short)(ACC_PUBLIC|ACC_STATIC);
        
        s << (unsigned short)method->constMethodref->const2->const1->id;
        
        s << (unsigned short)method->constMethodref->const2->const2->id;

		s << (unsigned short) 1;

        writeCodeAttribute(s, currentClass, method);
    }
}

void writeCodeAttribute(QDataStream & s, struct SemanticClass *  currentClass, struct SemanticMethod * method)
{
	if(method->funcdef != NULL)
	{
		QByteArray * byteArray = createByteCode(currentClass, method);
    
		s << (unsigned short) 1;
    
		s << (unsigned int) (12 + byteArray->size());
    
		s << (unsigned short) 100;
    
		s << (unsigned short) (method->localVariableTable.size());  
    
		s << *byteArray;
    
		s << (unsigned short) 0;

		s << (unsigned short) 0;
	}
}

QByteArray * createByteCode(struct SemanticClass * currentClass, struct SemanticMethod * method)
{
	bytes = new QByteArray();
    bytes->clear();
    QDataStream code(bytes, QIODevice::ReadWrite);
    
	createByteCodeFromStatementList(method->funcdef->stmtList, code, currentClass, method);
    
	if (method->retType == NULL || method->retType->type == EVoid) {
		code << _RETURN;
	}
    
    return bytes;
}

void createByteCodeFromStatement(struct StStmt * stmt, QDataStream & c, struct SemanticClass *  currentClass, struct SemanticMethod * method)
{
	if(stmt == NULL)
        return;
	int second,first,start,if1,if2,if_start,else1,else2,else_start;
	 switch(stmt->type)
	 {
	 case EnTypeStmt::EnExpr:
		 createByteCodeFromExpr(stmt->expr, c, currentClass, method);
         break;
	 case EnTypeStmt::EnWhile:
		first = bytes->size();
        c << GOTO;
        start = bytes->size();
        c << (unsigned short) 0;
		createByteCodeFromStatementList(stmt->stmtList, c, currentClass, method);
        second = bytes->size();  
        CalcOffset(second - first, start);
        second = bytes->size();
        c << IFNE;  
        start = bytes->size();
        c << (unsigned short) 0;       
		CalcOffset(first + 3 - second, start);
		break;
	 case EnTypeStmt::EnLoop:
		first = bytes->size();
		c << GOTO;
        start = bytes->size();
        c << (unsigned short) 0;
		createByteCodeFromStatementList(stmt->stmtList, c, currentClass, method);
        second = bytes->size();  
        CalcOffset(second - first, start);
		createByteCodeFromExpr(stmt->expr, c, currentClass, method);
        second = bytes->size();
		c << ALOAD_1;
		c << ALOAD_0;
        c << IFNE;
        start = bytes->size();
        c << (unsigned short) 0;       
		CalcOffset(first + 3 - second, start);
		break;
	 case EnTypeStmt::EnIf:
		else1;
        else_start;
		createByteCodeFromExpr(stmt->expr, c, currentClass, method);
        if1 = bytes->size();
        c << IFNE;
        if_start = bytes->size();
        c << (unsigned short) 0;  
                
		if(stmt->stmtList != NULL)
        {
			createByteCodeFromStatementList(stmt->stmtList, c, currentClass, method);
            else1 = bytes->size();
            c << GOTO;
            else_start = bytes->size();
            c << (unsigned short) 0;
        }
        else
        {
            else1 = bytes->size();
            c << GOTO;
            else_start = bytes->size();
            c << (unsigned short) 0;
        }
        if2 = bytes->size();
        CalcOffset(if2-if1, if_start);
		createByteCodeFromStatementList(stmt->ifStmtCondition->body, c, currentClass, method);
        else2 = bytes->size();
        CalcOffset(else2-else1, else_start);
		 break;
	 case EnTypeStmt::EnReturn:
		 createByteCodeFromExpr(stmt->expr, c, currentClass, method);
            if(stmt->expr != NULL)
            {
				switch(stmt->expr->type)
                {
				case EnTypeSpec::EChar:
				case EnTypeSpec::EInteger:
				case EnTypeSpec::EReal:
				case EnTypeSpec::EBoolean:
                        c << IRETURN;
                        break;
				case EnTypeSpec::ESet:
				case EnTypeSpec::EString:
                        c << ARETURN;
                        break;
                }
            }
            else
            {
                c << _RETURN;
            }
		 break;
	 }
}

void createByteCodeFromStatementList(struct StStmtList * list, QDataStream & c, struct SemanticClass *  currentClass, struct SemanticMethod * method)
{
	if(list != NULL)
    {
        while(list != NULL)
        {
			struct StStmt * stmt = list->current;
			createByteCodeFromStatement(stmt, c, currentClass, method);
			list = list->next;
        }
    }
}

void createByteCodeFromExpr(struct StExpr * expr, QDataStream & c, struct SemanticClass *  currentClass, struct SemanticMethod * method)
{
	QString classname;
	struct StSetList* list;
	int indexSet;
	struct StExprList * arg;

	if (expr == NULL)
		return;

	unsigned char id = 0;
    unsigned short ids = 0;
	switch (expr->type)
	{
	case EnAssignment:
		switch (expr->typeSpec->type)
		{
		case EChar:
		case EInteger:
		case EBoolean:
			if (expr->typeSpec->_array != NULL)
			{
				if (currentClass->fieldsTable.contains(expr->Id))
				{
					c << ALOAD_0;
                    createByteCodeFromExpr(expr->right, c, currentClass, method);  
					ids = (unsigned short)(findMethodInConstants(currentClass->constantsTable, "BASEIO", QString("ToArrayChar")));
					c << INVOKESTATIC << ids;

					for (int i = 0; i < currentClass->constantsTable.count(); i ++)
					{
						if (currentClass->constantsTable.value(i)->utf8Const == QString(expr->Id))
						{
							ids = (unsigned short)currentClass->constantsTable.value(i)->id;
							break;
						}
					}
                    c << PUTFIELD << ids;
				}
				else
				{
					createByteCodeFromExpr(expr->right, c, currentClass, method);

					ids = (unsigned short)(findMethodInConstants(currentClass->constantsTable, "BASEIO", QString("ToArrayChar")));
					c << INVOKESTATIC << ids;
					c << ISTORE << (unsigned char)(expr->left->numberOfTable);
				}
			}
			else
			{
				if (currentClass->fieldsTable.contains(expr->Id))
				{
					c << ALOAD_0;
                    createByteCodeFromExpr(expr->right, c, currentClass, method);  
					for (int i = 0; i < currentClass->constantsTable.count(); i ++)
					{
						if (currentClass->constantsTable.value(i)->utf8Const == QString(expr->Id))
						{
							ids = (unsigned short)currentClass->constantsTable.value(i)->id;
							break;
						}
					}
                    c << PUTFIELD << ids;
				}
				else
				{
					createByteCodeFromExpr(expr->right, c, currentClass, method);                        
					c << ISTORE << (unsigned char)(expr->left->numberOfTable);
				}
			}
			break;
		case ESet:
			if (currentClass->fieldsTable.contains(expr->Id))
			{
				c << ALOAD_0;
                createByteCodeFromExpr(expr->right, c, currentClass, method);  
				for (int i = 0; i < currentClass->constantsTable.count(); i ++)
				{
					if (currentClass->constantsTable.value(i)->utf8Const == QString(expr->Id))
					{
						ids = (unsigned short)currentClass->constantsTable.value(i)->id;
						break;
					}
				}
                c << PUTFIELD << ids;
			}
			else
			{
				createByteCodeFromExpr(expr->right, c, currentClass, method);
				c << ASTORE << (unsigned char)(expr->left->numberOfTable);
			}
			break;
		case EReal:
			if (currentClass->fieldsTable.contains(expr->Id))
			{
				c << ALOAD_0;
                createByteCodeFromExpr(expr->right, c, currentClass, method);  
				for (int i = 0; i < currentClass->constantsTable.count(); i ++)
				{
					if (currentClass->constantsTable.value(i)->utf8Const == QString(expr->Id))
					{
						ids = (unsigned short)currentClass->constantsTable.value(i)->id;
						break;
					}
				}
                c << PUTFIELD << ids;
			}
			else
			{
				createByteCodeFromExpr(expr->right, c, currentClass, method);                        
				c << FSTORE << (unsigned char)(expr->left->numberOfTable);
			}
			break;
		}
		break;
	case EnOr:
		createByteCodeFromExpr(expr->left, c, currentClass, method);
        createByteCodeFromExpr(expr->right, c, currentClass, method);
        c << IADD;
        c << ICONST_0;
            
        c << IF_ICMPEQ << (signed short)7;
        c << ICONST_1;
        c << GOTO << (signed short)4;
        c << ICONST_0;
		break;
	case EnAnd:
		createByteCodeFromExpr(expr->left, c, currentClass, method);
        createByteCodeFromExpr(expr->right, c, currentClass, method);
        c << IMUL;
        c << ICONST_0;
            
        c << IF_ICMPEQ << (signed short)7;
        c << ICONST_1;
        c << GOTO << (signed short)4;
        c << ICONST_0;
		break;
	case EnLess:
		createByteCodeFromExpr(expr->left, c, currentClass, method);
        createByteCodeFromExpr(expr->right, c, currentClass, method);
        c << IF_ICMPGE << (signed short)7;
        c << ICONST_1;
        c << GOTO << (signed short)4;
        c << ICONST_0;
		break;
	case EnMore:
		createByteCodeFromExpr(expr->left, c, currentClass, method);
        createByteCodeFromExpr(expr->right, c, currentClass, method);
        c << IF_ICMPGE << (signed short)7;
        c << ICONST_0;
        c << GOTO << (signed short)4;
        c << ICONST_1;
		break;
	case EnLessEqually:
		createByteCodeFromExpr(expr->left, c, currentClass, method);
        createByteCodeFromExpr(expr->right, c, currentClass, method);
        c << IF_ICMPGT << (signed short)7;
        c << ICONST_1;
        c << GOTO << (signed short)4;
        c << ICONST_0;
		break;
	case EnMoreEqually:
		createByteCodeFromExpr(expr->left, c, currentClass, method);
        createByteCodeFromExpr(expr->right, c, currentClass, method);
        c << IF_ICMPGT << (signed short)7;
        c << ICONST_0;
        c << GOTO << (signed short)4;
        c << ICONST_1;
		break;
	case EnCompare:
		createByteCodeFromExpr(expr->left, c, currentClass, method);
        createByteCodeFromExpr(expr->right, c, currentClass, method);
        c << IF_ICMPNE << (signed short)7;
        c << ICONST_1;
        c << GOTO << (signed short)4;
        c << ICONST_0;
		break;
	case EnNotCompare:
		createByteCodeFromExpr(expr->left, c, currentClass, method);
        createByteCodeFromExpr(expr->right, c, currentClass, method);
        c << IF_ICMPNE << (signed short)7;
        c << ICONST_0;
        c << GOTO << (signed short)4;
        c << ICONST_1;
		break;
	case EnPlus:
		switch (expr->typeSpec->type)
		{
		case EInteger:
			createByteCodeFromExpr(expr->left, c, currentClass, method);
            createByteCodeFromExpr(expr->right, c, currentClass, method);
            c << IADD;
			break;
		case EReal:createByteCodeFromExpr(expr->left, c, currentClass, method);
            createByteCodeFromExpr(expr->right, c, currentClass, method);
            c << FADD;
			break;
		case ESet:
			createByteCodeFromExpr(expr->left, c, currentClass, method);
			createByteCodeFromExpr(expr->right, c, currentClass, method);
					
			ids = (unsigned short)(findMethodInConstants(currentClass->constantsTable, "Set", QString("UnSet")));
			c << INVOKESTATIC << ids;
			break;
		}
		break;
	case EnMinus:
		switch (expr->typeSpec->type)
		{
		case EInteger:
			createByteCodeFromExpr(expr->left, c, currentClass, method);
            createByteCodeFromExpr(expr->right, c, currentClass, method);
            c << ISUB;
			break;
		case EReal:createByteCodeFromExpr(expr->left, c, currentClass, method);
            createByteCodeFromExpr(expr->right, c, currentClass, method);
            c << FSUB;
			break;
		case ESet:
			createByteCodeFromExpr(expr->left, c, currentClass, method);
			createByteCodeFromExpr(expr->right, c, currentClass, method);
					
			ids = (unsigned short)(findMethodInConstants(currentClass->constantsTable, "Set", QString("DifSet")));
			c << INVOKESTATIC << ids;
			break;
		}
		break;
	case EnMultiply:
		switch (expr->typeSpec->type)
		{
		case EInteger:
			createByteCodeFromExpr(expr->left, c, currentClass, method);
            createByteCodeFromExpr(expr->right, c, currentClass, method);
            c << IMUL;
			break;
		case EReal:createByteCodeFromExpr(expr->left, c, currentClass, method);
            createByteCodeFromExpr(expr->right, c, currentClass, method);
            c << FMUL;
			break;
		case ESet:
			createByteCodeFromExpr(expr->left, c, currentClass, method);
			createByteCodeFromExpr(expr->right, c, currentClass, method);
					
			ids = (unsigned short)(findMethodInConstants(currentClass->constantsTable, "Set", QString("IntSet")));
			c << INVOKESTATIC << ids;
			break;
		}
		break;
	case EnDivision:
	case EnDiv:
		switch (expr->typeSpec->type)
		{
		case EInteger:
			createByteCodeFromExpr(expr->left, c, currentClass, method);
            createByteCodeFromExpr(expr->right, c, currentClass, method);
            c << IDIV;
			break;
		case EReal:
			createByteCodeFromExpr(expr->left, c, currentClass, method);
            createByteCodeFromExpr(expr->right, c, currentClass, method);
            c << FDIV;
			break;
		case ESet:
			createByteCodeFromExpr(expr->left, c, currentClass, method);
			createByteCodeFromExpr(expr->right, c, currentClass, method);
					
			ids = (unsigned short)(findMethodInConstants(currentClass->constantsTable, "Set", QString("SdifSet")));
			c << INVOKESTATIC << ids;
			break;
		}
		break;
	case EnMod:
		createByteCodeFromExpr(expr->left, c, currentClass, method);
        createByteCodeFromExpr(expr->right, c, currentClass, method);
        c << IREM;
		break;
	case EnNot:
		createByteCodeFromExpr(expr->left, c, currentClass, method);
        c << IFNE << (signed short)7;
        c << ICONST_1;
        c << GOTO << (signed short)4;
        c << ICONST_0;
		break;
	case EnUnMinus:
		switch(expr->typeSpec->type)
		{
		case EInteger:
			c << SIPUSH << (unsigned short)0;
			createByteCodeFromExpr(expr->right, c, currentClass, method);
			c << ISUB;
			break;
		case EReal:
			c << SIPUSH << (unsigned short)0;
			createByteCodeFromExpr(expr->right, c, currentClass, method);
			c << FSUB;
			break;
		}
		break;
	case EnId:
		if(currentClass->fieldsTable.contains(expr->Id))
        {
			c << ALOAD_0;
			for (int i = 0; i < currentClass->constantsTable.count(); i ++)
			{
				if (currentClass->constantsTable.value(i)->utf8Const == QString(expr->Id))
				{
					ids = (unsigned short)currentClass->constantsTable.value(i)->id;
					break;
				}
			}
            c << GETFIELD << (unsigned short)(ids);
        }
        else{
			switch(expr->typeSpec->type)
            {
            case EChar:
            case EInteger:
			case EBoolean:
                c << ILOAD << (unsigned char)expr->numberOfTable;
                break;
            case ESet:
                break;
			case EReal:
                c << FLOAD << (unsigned char)expr->numberOfTable;
                break;
            }
        }
		break;
	case EnSet:
		c << BIPUSH << countList(expr->set->list);
		if (expr->set->list->currentId == NULL)
			c << NEWARRAY << (unsigned char)10;
		else
			c << NEWARRAY << (unsigned char)5;
		list = expr->set->list;
		indexSet = 0;
		while (list != NULL)
		{
			c << DUP;
			c << BIPUSH << (unsigned char)indexSet;
			indexSet++;
			if (expr->set->list->currentId == NULL)
				c << SIPUSH << (unsigned short)list->currentInt;
			else
				c << SIPUSH << (unsigned short)list->currentId;
			list = list->next;
			c << IASTORE;
		}
		if (expr->set->list->currentId == NULL)
			ids = (unsigned short)(findMethodInConstants(currentClass->constantsTable,  "Set", "CreateSetInteger"));
		else
			ids = (unsigned short)(findMethodInConstants(currentClass->constantsTable,  "Set", "CreateSetCharacter"));

        c << INVOKESTATIC << ids; 
		if(currentClass->fieldsTable.contains(expr->left->Id))
		{
			for (int i = 0; i < currentClass->constantsTable.count(); i ++)
			{
				if (currentClass->constantsTable.value(i)->utf8Const == QString(expr->left->Id))
				{
					ids = (unsigned short)currentClass->constantsTable.value(i)->id;
					break;
				}
			}
            c << PUTFIELD << ids;
		}
		else
			c << ASTORE << (unsigned char)expr->numberOfTable;
		break;
	case EnFunc:
		classname = QString(currentClass->constantsTable[1]->utf8Const);
                
        if(QString(expr->Id) == QString("LEN") || QString(expr->Id) == QString("CAP")|| QString(expr->Id) == QString("CHR") ||
			QString(expr->Id) == QString("COPY") || QString(expr->Id) == QString("DEC") || QString(expr->Id) == QString("INC"))
            classname = QString(BASEIO);
                    
        if(expr->list != NULL)
        {
            arg = expr->list;
            while(arg != NULL)
            {
				createByteCodeFromExpr(arg->current, c, currentClass, method);
                arg = arg->next;
            }
        }
        ids = (unsigned short)(findMethodInConstants(currentClass->constantsTable,  classname, QString(expr->Id))->id);
        c << INVOKESTATIC << ids;
		break;
	case EnIn:
		createByteCodeFromExpr(expr->left, c, currentClass, method);
		createByteCodeFromExpr(expr->right, c, currentClass, method);
					
		ids = (unsigned short)(findMethodInConstants(currentClass->constantsTable, "Set", QString("IN")));
		c << INVOKESTATIC << ids;
		break;
	case EnConvertType:
		if (expr->typeSpec->type == EReal)
		{
			createByteCodeFromExpr(expr->left, c, currentClass, method);
			c << I2F;
		}
		else
		{
			createByteCodeFromExpr(expr->left, c, currentClass, method);
			c << F2I;
		}
		break;
	case EnArrayAccess:
					//TOOOOO DOOOOO
		break;
	case EnArrayAssign:
		if(currentClass->fieldsTable.contains(expr->Id))
		{
			c << ALOAD_0;
			for (int i = 0; i < currentClass->constantsTable.count(); i ++)
			{
				if (currentClass->constantsTable.value(i)->utf8Const == QString(expr->Id))
				{
					ids = (unsigned short)currentClass->constantsTable.value(i)->id;
					break;
				}
			}
			c << GETSTATIC << ids;
			c << BIPUSH << (unsigned char)expr->indexArray->constInt;
			//TO DO
		}
		break;
	case EnString:
					//TOOOOO DOOOOO
		break;
	case EnChar:
		c << BIPUSH << (unsigned char)(int)(expr->Id[0]);
		break;
	case EnInt:
		id = addIntToConstants(currentClass->constantsTable, expr->constInt)->id;
		c << LDC << id;
		break;
	case EnReal:
		id = addRealToConstants(currentClass->constantsTable, expr->constInt)->id;
		c << LDC << id;
		break;
	}
}

struct SemanticConstant * findMethodInConstants(QList<struct SemanticConstant *> & constantsTable, QString classname, QString methodname)
{
    struct SemanticConstant * constClass = addClassToConstants(constantsTable, classname);
    
    struct SemanticConstant * name = addUtf8ToConstants(constantsTable, methodname);

   
    for(int i=0; i < constantsTable.size(); ++i)
    {
        if(
            constantsTable[i]->type == CONSTANT_METHODREF &&
            constantsTable[i]->const1 == constClass &&
            constantsTable[i]->const2->const1 == name
          )
            return constantsTable[i];
    }    
    return NULL;
}